package atom.core0.http;

/**
 * <p>Title: </p>
 * <p>Description:</p>
 * <p>Copyright: Copyright (c) 2011.12</p>
 * @author hike_man@126.com
 * @version 1.0
 */
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;

import atom.core0.bean.general.ValuePair;
import atom.core0.exception.BizConnectException;
import atom.core0.exception.BizException;
import atom.core0.exception.BizSysException;
import atom.core0.exception.UnknowStateException;
import atom.core0.object.ObjectUtil;
import atom.core0.util.StreamUtil;
import atom.core0.util.security.JDigest;

public class HttpAgent {
	private String charset;
	private String boundary = "---------------------------7d4a6d158c9";
	private URL url;
	private int connecttimeout; // 秒
	private int readtimeout; // 秒
	protected ArrayList parmlist = new ArrayList();
	protected ArrayList filelist = new ArrayList();
	protected HashMap headMap = new HashMap();
	private InputStream inputStream;
	private HttpURLConnection httpConnection;
	private List<String> cookieList = null;

	private static SSLSocketFactory ssf = null;
	private static Proxy proxy = null;

	public HttpAgent(String charset, int connecttimeout, int readtimeout) throws MalformedURLException {
		this.connecttimeout = connecttimeout;
		this.readtimeout = readtimeout;
		this.charset = charset;// "GBK" "UTF-8"

	}

	// 如果没有特殊的处理，可以不用设置
	// 如果要设置特殊算法 特殊Manager，则可以构造设置
	public static void setSSLSocketFactory(SSLSocketFactory ssff) throws Exception {
		ssf = ssff;

	}

	public static void setProxy(String ip, int port) throws Exception {
		InetSocketAddress addr = new InetSocketAddress(ip, port);
		proxy = new Proxy(Proxy.Type.HTTP, addr);
	}

	public static void clearProxy() throws Exception {
		proxy = null;
	}

	//可以在openConnection之后调用
	public void setCharset(String charset) {
		this.charset = charset;
	}

	public HttpURLConnection openConnection(String urlstr) throws BizConnectException {
		try {
			this.url = new URL(urlstr);
			if (proxy == null)
				httpConnection = (HttpURLConnection) url.openConnection();
			else
				httpConnection = (HttpURLConnection) url.openConnection(proxy);

			httpConnection.setDoInput(true);
			httpConnection.setDoOutput(true);// get请求用不到 get请求默认false;post请求必须打开
			// httpConnection.setRequestProperty("Content-Type",
			// "x-application/myremote");
			Iterator it = headMap.entrySet().iterator();
			while (it.hasNext()) {
				Map.Entry entry = (Map.Entry) it.next();
				String key = (String) entry.getKey();
				String value = (String) entry.getValue();
				httpConnection.addRequestProperty(key, value);
			}

			// Set the post method. Default is GET
			// 如果这里使用httpConnection.getOutputStream则前面设置的GET无效，转为POST

			httpConnection.setConnectTimeout(connecttimeout * 1000);
			httpConnection.setReadTimeout(readtimeout * 1000);
			// Post 请求不能使用缓存
			httpConnection.setUseCaches(false);

			// 如果是https请求
			if (httpConnection instanceof HttpsURLConnection) {
				HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
				if (ssf != null)
					httpsConnection.setSSLSocketFactory(ssf);
			}
		} catch (Exception e) {
			throw new BizConnectException(e, "Url连接失败[" + urlstr + "]");
		}
		return httpConnection;

	}

	public List<String> getCookieList() {
		return cookieList;
	}

	public String getCookieString() {
		Iterator<String> it = cookieList.iterator();
		StringBuffer sbu = new StringBuffer();
		while (it.hasNext()) {
			sbu.append(it.next());
		}
		return sbu.toString();
	}

	private void getCookes() {
		String cookieskey = "Set-Cookie";
		Map<String, List<String>> maps = httpConnection.getHeaderFields();
		cookieList = maps.get(cookieskey);
	}

	public void addParm(String name, String value) {
		ValuePair namevaluepair = new ValuePair(name, value);
		parmlist.add(namevaluepair);
		return;
	}

	public void addFile(String name, String filename, String localfilename) throws Exception {
		FileNameValue filenamevalue = new FileNameValue(name, filename, localfilename);
		filelist.add(filenamevalue);
		return;
	}

	public void postObject(String urlstr, Serializable inputObj) throws BizConnectException, BizSysException,
			UnknowStateException {
		OutputStream output = null;
		byte[] body = null;
		// 方法2
		try {
			// zhaomc 2013.12.20
			// 方法1 转换成ObjectOutputStream
			// output.writeObject(inputObj);
			body = ObjectUtil.object2Bytes(inputObj);

		} catch (Exception e) {
			throw new BizSysException(e, "序列化Object信息失败[" + url + "]");
		}
		openConnection(urlstr);
		postInfo(null, body);

	}

	// 签名通讯 。第1位，算法；然后256位签名或者md5
	// 10位通讯头 包含算法，将来tcp通讯保存实际报文长度
	// 256位签名。
	// 实际报文
	public void postObject(String urlstr, Serializable inputObj, String md5Key) throws BizConnectException,
			BizSysException, UnknowStateException {
		OutputStream output = null;
		byte[] body = null;
		byte[] head = null;
		// 方法2
		try {
			// zhaomc 2013.12.20
			// 方法1 转换成ObjectOutputStream
			// output.writeObject(inputObj);
			body = ObjectUtil.object2Bytes(inputObj);

			byte[] md5 = JDigest.digestMD5(body, md5Key);
			head = new byte[10 + 256];
			System.arraycopy("MD5", 0, head, 0, 3);// 头
			System.arraycopy(md5, 0, head, 10, md5.length);// 签名
		} catch (Exception e) {
			throw new BizSysException(e, "序列化Object头信息失败[" + url + "]");
		}

		openConnection(urlstr);
		postInfo(head, body);
	}

	public void postBytes(String urlstr, byte[] bytes) throws BizSysException, BizConnectException,UnknowStateException {
		openConnection(urlstr);
		postInfo(null, bytes);
	}

	private void postInfo(byte[] head, byte[] body) throws BizSysException, BizConnectException,UnknowStateException {
		OutputStream output = null;
		try {
			httpConnection.setRequestMethod("POST");
			output = httpConnection.getOutputStream();
		} catch (Throwable e) {
			// ConnectException端口号不对 getOutputStream 直接抛异常
			throw new BizConnectException(e, "Url连接失败[端口号不存在][" + url + "]");
		}
		try {
			// 签名等信息
			if (head != null)
				output.write(head);

			output.write(body);
			output.flush();
			output.close();
			inputStream = httpConnection.getInputStream();
		} catch (SocketTimeoutException e) {
			// SocketTimeoutException超时
			// ConnectException 连接进入对方服务后被断开，其实，对方可能已经处理，所以应该当超时
			throw new UnknowStateException(e, "http连接成功后,读回执超时[" + url + "]");
		} catch (ConnectException e) {
			// SocketTimeoutException超时
			// ConnectException 连接进入对方服务后被断开，其实，对方可能已经处理，所以应该当超时
			throw new UnknowStateException(e, "http连接成功后,连接被断开[" + url + "]");
		} catch (SocketException e) {
			if (e.getMessage().indexOf("reset") > 0)
				throw new UnknowStateException(e, "连接被对方断开(Connection reset)");
		}

		catch (FileNotFoundException e) {
			// FileNotFoundException 端口存在 服务名字写错
			throw new BizSysException(e, "http连接失败[端口号存在,服务不存在][" + url + "]");
		} catch (IOException e)// FileNotFoundException
		{
			// FileNotFoundException 端口存在 服务名字写错
			throw new BizSysException(e, "http连接失败,IOException[" + url + "]");
		} catch (Exception e) {
			// SocketTimeoutException超时
			// ConnectException 连接进入对方服务后被断开，其实，对方可能已经处理，所以应该当超时
			throw new UnknowStateException(e, "http连接其他异常[" + url + "]");
		}
		// 对于hessian也是这样
		// HessianConnectionException 端口存在 名字写错;超时;服务断开连接（超时）
		// HessianRuntimeException 端口号不存在;

	}

	public void addRequestHeader(String headerName, String headerValue) {
		headMap.put(headerName, headerValue);
	}

	// 服务器端关闭链接 TIME_WAIT会出现在服务器端
	public void setServerSocketClose() {
		headMap.put("Connection", "close");
	}

	// public void sendPost(String urlstr) throws Exception {
	// openConnection(urlstr);
	// httpConnection.setRequestMethod("POST");
	//
	// if (filelist.size() == 0)
	// postSimple();
	// else
	// postMulti();
	// }

	// 某些网站只能能GET请求
	public void sendGet(String urlstr) throws BizException, UnknowStateException {
		// get请求是，openConnection 数据就已经发过去了，不存在连接失败问题
		openConnection(urlstr);
		try {
			httpConnection.setRequestMethod("GET");
			inputStream = httpConnection.getInputStream();
		} catch (Exception e) {
			throw new UnknowStateException(e, "GET请求获取回执失败[" + urlstr + "]");
		}
	}

	public void sendPostSimple(String urlstr) throws BizException, UnknowStateException {
		String content = "";
		try {
			// 汉字会转码
			for (int i = 0; i < parmlist.size(); i++) {
				ValuePair pair = (ValuePair) parmlist.get(i);
				content += pair.getCode() + "=" + URLEncoder.encode(pair.getDesc(), charset);
				// 不是最后一个参数
				if (parmlist.size() != (i + 1))
					content += "&";
			}
		} catch (Exception e) {
			throw new BizException(e, "生成HTTP参数信息失败[" + url + "]");
		}
		openConnection(urlstr);
		httpConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
		httpConnection.setRequestProperty("Content-Length", String.valueOf(content.length()));
		// post
		postInfo(null, content.getBytes());

	}
	//最后改造
	public void sendPostMulti() throws Exception {
		// String[] props = ... // 字段名
		// String[] values = ... // 字段值
		// byte[] file = ... // 文件内容
		StringBuffer sb1 = new StringBuffer();
		// 发送每个字段:
		for (int i = 0; i < parmlist.size(); i++) {
			ValuePair pair = (ValuePair) parmlist.get(i);
			sb1 = sb1.append("--");
			sb1 = sb1.append(boundary);
			sb1 = sb1.append("\r\n");
			sb1 = sb1.append("Content-Disposition: form-data; name=\"" + pair.getCode() + "\"\r\n\r\n");
			sb1 = sb1.append(URLEncoder.encode(pair.getDesc(), charset));
			sb1 = sb1.append("\r\n");
		}

		// 发送文件:
		int filelen = 0;
		int fileheadlen = 0;
		for (int i = 0; i < filelist.size(); i++) {
			FileNameValue pair = (FileNameValue) filelist.get(i);
			StringBuffer sb2 = new StringBuffer();
			// 从第二个文件开始都要多加一个\r\n
			if (i > 1)
				sb2 = sb2.append("\r\n");
			sb2 = sb2.append("--");
			sb2 = sb2.append(boundary);
			sb2 = sb2.append("\r\n");
			// sb =
			// sb.append("Content-Disposition: form-data; name=\"1\"; filename=\"1.txt\"\r\n");

			sb2 = sb2.append("Content-Disposition: form-data; name=\"" + pair.getName() + "\"; filename=\""
					+ pair.getFilename() + "\"\r\n");
			sb2 = sb2.append("Content-Type: application/octet-stream\r\n\r\n");
			byte[] data = sb2.toString().getBytes();
			pair.setFilehead(data);

			fileheadlen += data.length;
			filelen += pair.getFile().length();
		}
		byte[] end_data = ("\r\n--" + boundary + "--\r\n").getBytes();
		httpConnection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary + ";charset="
				+ charset);
		httpConnection.setRequestProperty("Content-Length",
				String.valueOf(sb1.toString().getBytes().length + fileheadlen + filelen + end_data.length));
		// 输出:

		DataOutputStream output = new DataOutputStream(httpConnection.getOutputStream());
		output.write(sb1.toString().getBytes());

		for (int i = 0; i < filelist.size(); i++) {
			FileNameValue pair = (FileNameValue) filelist.get(i);
			output.write(pair.getFilehead());
			outputFile(output, pair.getFile());
			output.flush();
		}
		output.write(end_data);
		output.flush();
		output.close();

		inputStream = httpConnection.getInputStream();

	}

	private void outputFile(DataOutputStream output, File file) throws Exception {
		FileInputStream fin = new FileInputStream(file);

		byte[] buf = new byte[1024];
		int num;
		while ((num = fin.read(buf, 0, buf.length)) != -1) {
			output.write(buf, 0, num);
			output.flush();
		}
	}

	public byte[] getResponseBody() throws UnknowStateException {
		byte[] responseBody = null;
		try {
			responseBody = StreamUtil.getByteArrayFromInputStream(inputStream);
		} catch (Exception e) {
			throw new UnknowStateException(e, "getResponseBody函数遇到关闭的流");
		}
		disconnect();
		return responseBody;
	}

	// 第二种，getResponseBodyAsString，这个方法返回的是String类型，值得注意的是该方法返回的String的编码是根据系统默认的编码方式，所以返回的String值可能编码类型有误，在本文的"字符编码"部分中将对此做详细介绍；
	public String getResponseBodyAsString() throws UnknowStateException {
		String responseBody = "";
		try {
			responseBody = StreamUtil.getStringFromInputStream(inputStream, charset);
		} catch (Exception e) {
			throw new UnknowStateException(e, "getResponseBodyAsString函数遇到关闭的流");
		}
		disconnect();
		return responseBody;
	}

	// 第三种，getResponseJavaObject，这个方法对于目标地址中有大量数据需要传输是最佳的。但必须是java Object
	public Serializable getResponseJavaObject() throws UnknowStateException {
		Serializable returnObj = null;
		try {
			returnObj = StreamUtil.getJavaObjectFromInputStream(inputStream);
		} catch (Exception e) {
			throw new UnknowStateException(e, "getResponseJavaObject函数遇到关闭的流");
		}
		disconnect();
		return returnObj;
	}

	// 要主动关闭连接与流
	public InputStream getResponseBodyStream() throws Exception {
		return inputStream;
	}

	// 关闭了，流也关闭了
	public void disconnect() {
		getCookes();
		httpConnection.disconnect();
	}


}
